package org.zend.hudson;

import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.BuildListener;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.util.FormValidation;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import javax.servlet.ServletException;

import net.sf.json.JSONObject;

import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.zend.webapi.core.WebApiClient;
import org.zend.webapi.core.WebApiException;
import org.zend.webapi.core.connection.auth.BasicCredentials;
import org.zend.webapi.core.connection.auth.WebApiCredentials;
import org.zend.webapi.core.connection.data.ApplicationInfo;
import org.zend.webapi.core.connection.data.ApplicationsList;
import org.zend.webapi.core.connection.data.GenericResponseDataVisitor;
import org.zend.webapi.core.connection.data.SystemInfo;
import org.zend.webapi.core.connection.request.NamedInputStream;

/**
 * 
 * @author Roy, 2011
 * 
 */
public class HelloWorldBuilder extends Builder {

	private final String file;
	private final String pfile;
	private final String host;
	private final String key;
	private final String secret;
	private final String base;
	private final Boolean ignore;
	private final Boolean vhost;
	private final String appname;

	// Fields in config.jelly must match the parameter names in the
	// "DataBoundConstructor"
	@DataBoundConstructor
	public HelloWorldBuilder(String file, String pfile, String appname,
			String host, String key, String secret, String base, Boolean vhost,
			Boolean ignore) {
		this.file = file;
		this.pfile = pfile;
		this.appname = appname;
		this.host = host;
		this.key = key;
		this.secret = secret;
		this.base = base;
		this.vhost = vhost;
		this.ignore = ignore;
	}

	/**
	 * We'll use this from the <tt>config.jelly</tt>.
	 */
	public String getFile() {
		return file;
	}

	/**
	 * We'll use this from the <tt>config.jelly</tt>.
	 */
	public String getHost() {
		return host;
	}

	public String getPfile() {
		return pfile;
	}

	/**
	 * We'll use this from the <tt>config.jelly</tt>.
	 */
	public String getKey() {
		return key;
	}

	/**
	 * We'll use this from the <tt>config.jelly</tt>.
	 */
	public String getSecret() {
		return secret;
	}

	/**
	 * We'll use this from the <tt>config.jelly</tt>.
	 */
	public String getBase() {
		return base;
	}

	/**
	 * We'll use this from the <tt>config.jelly</tt>.
	 */
	public Boolean getVhost() {
		return vhost;
	}

	/**
	 * We'll use this from the <tt>config.jelly</tt>.
	 */
	public Boolean getIgnore() {
		return ignore;
	}

	/**
	 * We'll use this from the <tt>config.jelly</tt>.
	 */
	public String getAppname() {
		return appname;
	}

	@Override
	public boolean perform(AbstractBuild build, Launcher launcher,
			final BuildListener listener) {

		printStatus(build, listener);

		InputStream packageStream = readFile(getFile(), build, listener);
		if (packageStream == null) {
			return false;
		} else {
			listener.getLogger().println("zpk file is ready " + getFile());
		}

		InputStream parameterStream = null;
		if (getPfile() != null
				&& getPfile().trim().length() > 0) {
			parameterStream = readFile(getPfile(), build, listener);
			listener.getLogger().println(
					"Parameters file is ready " + getPfile());
		}

		try {
			WebApiCredentials credentials = new BasicCredentials(getKey(),
					getSecret());
			WebApiClient client = new WebApiClient(credentials, getHost());
			client.applicationDeploy(new NamedInputStream(packageStream,
					getFile()), getBase(), getIgnore(),
					getMap(parameterStream, listener), getAppname(),
					getVhost(), !getVhost());
			listener.getLogger().println(
					"Application deployed, checking status:");

			final ApplicationsList status = client.applicationGetStatus();
			status.accept(new GenericResponseDataVisitor() {
				@Override
				public boolean preVisit(ApplicationInfo applicationInfo) {
					listener.getLogger().println(
							"Application: " + applicationInfo.getAppName());
					listener.getLogger().println(
							"Status: " + applicationInfo.getStatus().getName());
					return true;
				}
			});

		} catch (WebApiException e) {
			listener.getLogger().println(e.getMessage());
			return false;
		} catch (MalformedURLException e) {
			listener.getLogger().println(e.getMessage());
			return false;
		}

		// how to consult with global
		// final boolean useFrench = getDescriptor().useFrench();
		return true;
	}

	protected void printStatus(AbstractBuild build, final BuildListener listener) {
		listener.getLogger().println("--------- Status ---------");
		listener.getLogger().println(
				"\tStart deploying application " + getAppname());
		listener.getLogger().println("\tPackage file " + getFile());
		listener.getLogger().println("\tHost name " + getHost());
		listener.getLogger().println(
				"\tkey is " + getKey() + " secret key is " + getSecret());
		listener.getLogger().println(
				"\tBase URL (use 'default-server' to annotate the default server host) : "
						+ base);
		listener.getLogger().println("\tIgnore Failures: " + getIgnore());
		try {
			listener.getLogger().println(
					"\tWorkspace: "
							+ build.getWorkspace().absolutize().toURI()
									.toString());
		} catch (Exception e1) {
			// ignore no need to resolve workspace name
		}
		listener.getLogger().println("--------- End Status ---------");
	}

	private Map<String, String> getMap(InputStream pFile, BuildListener listener) {
		if (pFile == null) {
			return null;
		}

		Properties p = new Properties();
		try {
			p.load(pFile);
		} catch (Exception e) {
			listener.getLogger().println(e.getMessage());
			return null;
		}

		Map<String, String> r = new HashMap<String, String>();
		final Set<Entry<Object, Object>> entrySet = p.entrySet();
		for (Entry<Object, Object> entry : entrySet) {
			r.put(entry.getKey().toString(), entry.getValue().toString());
		}
		return r;
	}

	protected InputStream readFile(String name, AbstractBuild build,
			final BuildListener listener) {
		final FilePath child = build.getWorkspace().child(name);

		try {
			return child.read();
		} catch (IOException e) {
			listener.getLogger().println(e.toString());
			return null;
		}
	}

	// overrided for better type safety.
	// if your plugin doesn't really define any property on Descriptor,
	// you don't have to do this.
	@Override
	public DescriptorImpl getDescriptor() {
		return (DescriptorImpl) super.getDescriptor();
	}

	/**
	 * Descriptor for {@link HelloWorldBuilder}. Used as a singleton. The class
	 * is marked as public so that it can be accessed from views.
	 * 
	 * <p>
	 * See <tt>views/hudson/plugins/hello_world/HelloWorldBuilder/*.jelly</tt>
	 * for the actual HTML fragment for the configuration screen.
	 */
	@Extension
	// this marker indicates Hudson that this is an implementation of an
	// extension point.
	public static final class DescriptorImpl extends
			BuildStepDescriptor<Builder> {
		/**
		 * To persist global configuration information, simply store it in a
		 * field and call save().
		 * 
		 * <p>
		 * If you don't want fields to be persisted, use <tt>transient</tt>.
		 */
		private boolean useFrench;

		/**
		 * Performs on-the-fly validation of the form field 'name'.
		 * 
		 * @param value
		 *            This parameter receives the value that the user has typed.
		 * @return Indicates the outcome of the validation. This is sent to the
		 *         browser.
		 */
		public FormValidation doCheckName(@QueryParameter String value)
				throws IOException, ServletException {
			if (value.length() == 0)
				return FormValidation.error("Please set a name");
			if (value.length() < 4)
				return FormValidation.warning("Isn't the name too short?");
			return FormValidation.ok();
		}

		public FormValidation doTestConnection(
				@QueryParameter("key") final String key,
				@QueryParameter("secret") final String secret,
				@QueryParameter("host") final String host) throws IOException,
				ServletException {

			if (key == null || secret == null || host == null
					|| key.length() == 0 || secret.length() == 0
					|| host.length() == 0) {

				return FormValidation
						.error("fields host, key and sceret key are mandatory");
			}

			try {
				WebApiCredentials credentials = new BasicCredentials(key,
						secret);
				WebApiClient client = new WebApiClient(credentials, host);
				final SystemInfo systemInfo = client.getSystemInfo();
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.append("Connection to ");
				stringBuilder.append(host);
				stringBuilder.append(" estabilshed. [Edition: ");
				stringBuilder.append(systemInfo.getEdition());
				stringBuilder.append(", version: ");
				stringBuilder.append(systemInfo.getVersion());
				stringBuilder.append(", statsus: ");
				stringBuilder.append(systemInfo.getStatus().getTitle());
				stringBuilder.append("]");
				return FormValidation.ok(stringBuilder.toString());
			} catch (WebApiException e) {
				return FormValidation
						.error("Error connecting to host, with error "
								+ e.getMessage());
			}
		}

		/**
		 * indicates that this builder can be used with all kinds of project
		 * types
		 */
		public boolean isApplicable(Class<? extends AbstractProject> aClass) {
			return true;
		}

		/**
		 * This human readable name is used in the configuration screen.
		 */
		public String getDisplayName() {
			return "Deploy";
		}

		@Override
		public boolean configure(StaplerRequest req, JSONObject formData)
				throws FormException {
			// To persist global configuration information,
			// set that to properties and call save().
			useFrench = formData.getBoolean("useFrench");
			// ^Can also use req.bindJSON(this, formData);
			// (easier when there are many fields; need set* methods for this,
			// like setUseFrench)
			save();
			return super.configure(req, formData);
		}

		/**
		 * This method returns true if the global configuration says we should
		 * speak French.
		 */
		public boolean useFrench() {
			return useFrench;
		}
	}
}
